import _ from 'lodash';
import { CmdOption } from "../CmdOption.js";
import fs from 'fs-extra';
import ini from 'ini';
import path from 'path';
import YAML from 'yaml';
import { BuildParams } from "../BuildParams.js";
import { BuildModeCfg, HostInfoDoc, HostIPCfg, PlatformCfg, TestSvrConfDoc, TestSvrConfNode } from '../typings';
import { ConnectConfig } from 'ssh2';
import { toolchain } from '../toolchain.js';
import { env } from '../env.js';
import { sendBuildFailureAlert } from '../tools/alert.js';

export class HostInfoParser {
    async getBuildCfg(option: CmdOption, params: BuildParams): Promise<void> {
        const hostCfgFile = path.join(option.oldToolsRoot, 'FYMGBuild/config/hostInfo.yml');
        const hostInfoContent = fs.readFileSync(hostCfgFile, 'utf-8');
        const hostInfoDoc = YAML.parse(hostInfoContent) as HostInfoDoc;

        const ymlContent = (hostInfoDoc as HostIPCfg)[params.ip];
        if(!ymlContent) {
            console.error(`未找到本机配置：${params.ip}`);
            process.exit(1);
        }
        params.hostCfg = ymlContent;
        const hostProjCfg = ymlContent['项目地址'];
        params.hostProjCfg = hostProjCfg;
        
        const projectName = params.projectName;
        const projectType = params.projectType;

        const iCfg = await toolchain.projIniCfgParser.getProjIniCfg(projectName);
        if (!iCfg) {
            console.log('未找到对应的.ini/.json配置！');
            process.exit(1);
        }

        const plat = option.webglRuntime == 'douyin' ? 'douyin' : option.platform.toLowerCase();

        let projCfg = hostProjCfg[projectName];
        if (!projCfg) {
            console.log('未在host.yml中配置项目，将自动生成配置');
            // 新项目不需要在hostInfo.yml中配置svn信息了，直接从config.ini中读取
            const [macHost, macPort, macUsername, macPassword, macWorkspace] = env.defaultMac.split(/[:@,]/);
            if (iCfg.Project.ProjectType == 'Laya') {
                if (!iCfg.Build?.ProjRoot) {
                    sendBuildFailureAlert('ini配置中需指定ProjRoot');
                    process.exit(1);
                }
                projCfg = {
                    autoGenerated: true,
                    // laya项目由于燕飞标新立异的目录名，采取在.ini中配置Build.ProjRoot的形式
                    develop: this.makeLayaBuildNodeCfg(iCfg.Project.code, iCfg.Build.ProjRoot, 'develop'),
                    publish: this.makeLayaBuildNodeCfg(iCfg.Project.code, iCfg.Build.ProjRoot, 'publish'),
                    macHost,
                    macPort,
                    macUsername,
                    macPassword,
                    macWorkspace,
                    '资源包': `/tmp/${iCfg.Project.code}_res`,
                    '服务器路径': iCfg.Project.code
                }
            } else {
                projCfg = {
                    autoGenerated: true,
                    // laya项目由于燕飞标新立异的目录名，采取在.ini中配置Build.ProjRoot的形式
                    develop: this.makeUnityBuildNodeCfg(option, iCfg.Project.code, iCfg.Build?.ProjRoot, 'develop'),
                    qatest: this.makeUnityBuildNodeCfg(option, iCfg.Project.code, iCfg.Build?.ProjRoot, 'qatest'),
                    publish: this.makeUnityBuildNodeCfg(option, iCfg.Project.code, iCfg.Build?.ProjRoot, 'publish'),
                    publishfix: this.makeUnityBuildNodeCfg(option, iCfg.Project.code, iCfg.Build?.ProjRoot, 'publish', true),
                    macHost,
                    macPort,
                    macUsername,
                    macPassword,
                    macWorkspace,
                    '资源包': `/tmp/${iCfg.Project.code}_${plat}_res`,
                    '服务器路径': iCfg.Project.code
                }
            }
        }
        if(!projCfg) {
            console.log(`未找到项目配置：${projectName}`);
            process.exit(1);
        }
        console.log('项目配置：', JSON.stringify(projCfg, null, 2));
        params.projCfg = projCfg;

        const buildNodeCfg = projCfg[projectType]!;

        params.uploadPath = path.join(hostProjCfg.resPath, buildNodeCfg.res);
        if (!path.isAbsolute(params.uploadPath)) {
            console.error(`资源目录(resPath + res)必须是绝对路径：${params.uploadPath}`);
            process.exit(1);
        }
        // 由于抖音采用版本号格式为主版本号.子版本号.修正版本号，故默认为1.0，构建时会自动附加末位版本号
        params.version = hostInfoDoc['版本号'][projectName] || (option.webglRuntime == 'douyin' ? '1.0' : '1.0.0');

        const platform = option.platform;
        const platformLC = platform.toLowerCase() as keyof BuildModeCfg;
        const platformCfg = buildNodeCfg[platformLC] as PlatformCfg;
        if(!platformCfg) {
            console.error(`未找到平台配置：${platform}`);
            process.exit(1);
        }

        // 这个client是历史遗留，没啥用了，为了兼容老的项目，以!结尾表示不自动添加后缀，默认还是都加上
        let projSuffixPath = 'client/project';
        if(platform == 'iOS') {
            // 检查现有项目如果使用了xxx/client/project的目录结构则继续使用
            const developNode = projCfg.develop[platformLC] as PlatformCfg;
            const devWorkSpacePath = path.join(hostProjCfg.ROOT, developNode.project);
            const oldStyleDevWorkSpacePath = path.join(devWorkSpacePath, projSuffixPath);
            params.developProjectPath = fs.existsSync(oldStyleDevWorkSpacePath) ? oldStyleDevWorkSpacePath : devWorkSpacePath;
        }

        // unity项目已经统一将包放至`${code}_${plat}_${type}`，不再读取hostinfo.yml这个恶心的配置
        params.packageLocalWebPath = toolchain.option.engine == 'unity' ? `${iCfg.Project.code}_${platformLC}_${projectType}` : platformCfg.package || '';
        if(!path.isAbsolute(params.packageLocalWebPath)) {
            params.packageLocalWebPath = path.join(hostProjCfg.packagePath, params.packageLocalWebPath);
        }

        // 检查现有项目如果使用了xxx/client/project的目录结构则继续使用
        if (toolchain.option.engine == 'unity' && toolchain.option.projectRoot) {
            params.workSpacePath = toolchain.option.projectRoot;
        } else {
            const oldStyleWorkSpacePath = path.join(platformCfg.project, projSuffixPath);
            params.workSpacePath = fs.existsSync(oldStyleWorkSpacePath) ? oldStyleWorkSpacePath : platformCfg.project;
            if(!path.isAbsolute(params.workSpacePath)) {
                params.workSpacePath = path.join(hostProjCfg.ROOT, params.workSpacePath);
            }
        }

        params.androidSdkRoot = hostProjCfg.sdkTool;
        params.androidSdkRoot_unity563 = hostProjCfg.sdkTool_unity563;

        params.defines = platformCfg.defines;

        params.macHost = projCfg.macHost || '';
        params.macPort = Number(projCfg.macPort || 0);
        params.macUsername = projCfg.macUsername|| '';
        params.macPassword = projCfg.macPassword || '';
        params.macWorkspace = projCfg.macWorkspace || '';
        
        let qrcodeUrl = 'http://' + params.ip;
        let qrcodeDir: string;
        if (ymlContent.url.match(/hudson$/)) {
            qrcodeDir = ymlContent.url + hostProjCfg.packagePath.replace(/.*hudson/, '');
        }
        else {
            qrcodeDir = hostProjCfg.packagePath.replace(hostProjCfg.ROOT, '');
        }
        qrcodeDir = qrcodeDir.replace(/\\+/g, '/');
        if (qrcodeDir.search(/:\d{4}\//) < 0) {
            qrcodeUrl += ':8282';
        }
        qrcodeUrl += qrcodeDir + '/' + platformCfg.package + '/';
        params.qrcodeUrl = qrcodeUrl;

        // 以下获取unity项目资源外发参数
        if (option.engine != 'laya') {
            // 以下目录之所以区分安卓ios和其他是为了兼容py脚本
            let verJsonPath: string = '';
            const publishCfg = projCfg.publish;
            if(publishCfg) {
                const nativecfg = publishCfg.android || publishCfg.ios;
                if(nativecfg) {
                    verJsonPath = path.join(params.workSpacePath, 'client/build/.cache/publish_res_lastver.json');
                } else {
                    verJsonPath = path.join(params.workSpacePath, '../build/.cache/publish_res_lastver.json');
                }
            }

            const srcDirPath = path.join(hostProjCfg.resPath, projCfg.publish.res);
            // unity项目已经统一将增量包放至`${code}_${plat}`，不再读取hostinfo.yml这个恶心的配置
            // 这里需要区分下plat，否则如果android和ios同时构，可能会导出删除tmp目录时报错
            const tmpResDir = option.engine == 'unity' ? `/tmp/${iCfg.Project.code}_${plat}_res` : projCfg['资源包'];
            const srcResfile = tmpResDir + '_src.tgz';
            const dstResfile = tmpResDir + '_dst.tgz';

            const tmpSrcMapDir = tmpResDir + '_map';
            const srcSrcMapfile = tmpSrcMapDir + '_src.tgz';
            const dstSrcMapfile = tmpSrcMapDir + '_dst.tgz';
    
            // 赋值给资源外发相关参数
            params.unityResPublishCfg = {srcDirPath, verJsonPath, tmpResDir, srcResfile, dstResfile, tmpSrcMapDir, srcSrcMapfile, dstSrcMapfile};
        }

        // 查询是否有服务器资源地址，有的话就走服务器，没有的话就走原来的
        const serverDir = projCfg['服务器路径'];
        let hostinfo: ConnectConfig;
        let cdnDir: string;
        if(serverDir) {
            cdnDir = hostInfoDoc['服务器根路径'] + serverDir;
            hostinfo = this.getNewTestHost(option, params);
        } else {
            // 远程服务器参数
            const oneSvrInfo = this.getCurCfg(option, params, platformCfg.svrinfo_key || projectName);
            cdnDir = oneSvrInfo.UPLOADPATH;
            hostinfo = {
                host: oneSvrInfo.IP, 
                port: Number(oneSvrInfo.PORT), 
                username: oneSvrInfo.USER
            };
            if(oneSvrInfo.PRIVATEKEY) {
                hostinfo.privateKey = fs.readFileSync(path.join(params.hostCfg.svrinfo, oneSvrInfo.PRIVATEKEY));
            } else {
                hostinfo.password = oneSvrInfo.PASSWD;
            }
        }
        let srcMapHostinfo = this.getWebBuglyHost(option, params);
        let srcMapCdnDir = `/root/webbugly/data/${serverDir}/sourcemap`;  
        params.cdnHostInfo = { hostinfo, cdnDir, srcMapHostinfo, srcMapCdnDir };
    }

    private makeLayaBuildNodeCfg(code: string, ws: string, type: 'develop' | 'publish'): BuildModeCfg {
        return {
            android: {
                project: ws.replace('${?}', type == 'develop' ? '' : '_Publish')
            },
            res: path.join(env.defaultResRoot, `${code}_${type}`)
        };
    }

    private makeUnityBuildNodeCfg(option: CmdOption, code: string, ws: string | undefined, type: 'develop' | 'publish' | 'qatest', isFix = false): BuildModeCfg {
        return {
            android: this.makeUnityPlatCfg(code, ws, 'android', type, isFix),
            ios: this.makeUnityPlatCfg(code, ws, 'ios', type, isFix),
            windows: this.makeUnityPlatCfg(code, ws, 'windows', type, isFix),
            webgl: this.makeUnityPlatCfg(code, ws, option.webglRuntime == 'douyin' ? 'douyin' : 'webgl', type, isFix),
            res: path.join(env.defaultResRoot, `${code}_${type}`)
        };
    }

    private makeUnityPlatCfg(code: string, ws: string | undefined, plat: 'android' | 'ios' | 'windows' | 'webgl' | 'douyin', type: 'develop' | 'publish' | 'qatest', isFix = false): PlatformCfg {
        return {
            project: toolchain.option.projectRoot ?? ws ?? path.join(env.defaultWorkspace, isFix ? `${code}_${plat}_${type}fix/project` : `${code}_${plat}_${type}/project`),
            package: `${code}_${plat}_${type}`,
            defines: 'PUBLISH._IL2CPP'
        };
    }

    /**获取总test机信息 */
    private getNewTestHost(option: CmdOption, params: BuildParams): ConnectConfig {
        const serverPath = path.join(option.oldToolsRoot, 'FYMGBuild/config/server');
        const secret = fs.readFileSync(serverPath, 'utf-8');
        const info = Buffer.from(secret , 'base64').toString().split(' ');

        return {
            host: info[0], 
            port: 22, 
            username: 'root', 
            password: info[1]
        };
    }

    /** 获取webbugly机信息 */
    private getWebBuglyHost(option: CmdOption, params: BuildParams): ConnectConfig {
        const serverPath = path.join(option.oldToolsRoot, 'FYMGBuild/config/server-bugly');
        const secret = fs.readFileSync(serverPath, 'utf-8');
        const info = Buffer.from(secret , 'base64').toString().split(' ');

        return {
            host: info[0], 
            port: 22, 
            username: 'root', 
            password: info[1]
        };
    }

    private getCurCfg(option: CmdOption, params: BuildParams, serverName: string): TestSvrConfNode {
        const svrinfo = ini.parse(fs.readFileSync(path.join(params.hostCfg.svrinfo, 'testSvr.conf'), 'utf-8')) as TestSvrConfDoc;
        for(let key in svrinfo) {
            const oneSvrInfo = svrinfo[key];
            if(oneSvrInfo.NAME == serverName) {
                return oneSvrInfo;
            }
        }

        console.error(`未在testSvr.conf中找到与${serverName}对应的配置`);
        process.exit(1);
    }
}